using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using System.Security.Cryptography;

namespace MainPaper
{
    static class MainPaper
    {
        private static BigInteger[] privateKeys;
        private static BigInteger[] publicKeys;
        private static BigInteger[] ri;
        private static BigInteger[] si;
        private static BigInteger[] ui;
        private static BigInteger p = 562949953422839;
        private static BigInteger g = 374528560633105;
        private static int n;
        private static MersenneTwister generator;
        private static SHA1 sha;
        private static bool _specialExecution;
        private static Dictionary<int,BigInteger> ps = new Dictionary<int, BigInteger>();
        private static Dictionary<int, BigInteger> gs = new Dictionary<int, BigInteger>();  


        public static long Execute(int numberOfParticipants, int bitlength, bool specialExecution)
        {
            _specialExecution = specialExecution;
            sha = new SHA1CryptoServiceProvider();
            generator = new MersenneTwister();
            if (ps.ContainsKey(bitlength))
            {
                p = ps[bitlength];
                g = gs[bitlength];
            }
            else
            {
                p = AlgoAndMath.NextSafePrime(BigInteger.Pow(2, bitlength));
                //p = BigInteger.Parse("281474976711563");
                //p = BigInteger.Parse("340282366920938463463374607431768223907");
                var k = (p - 1) / 2;
                g = AlgoAndMath.RandomBigInt(generator, p);
                while (BigInteger.ModPow(g, 2, p) == 1 || BigInteger.ModPow(g, k, p) == 1)
                {
                    g = AlgoAndMath.RandomBigInt(generator, p);
                }
                //g = BigInteger.Parse("86412248189911");
                //g = BigInteger.Parse("17891010277603318639");
                ps.Add(bitlength,p);
                gs.Add(bitlength,g);
            }
            
            
            n = numberOfParticipants;
            ri = new BigInteger[n];
            ui = new BigInteger[n];
            si = new BigInteger[n];
            publicKeys = new BigInteger[n];
            privateKeys = new BigInteger[n];
            for (int i = 0; i < n; i++)
            {
                privateKeys[i] = AlgoAndMath.RandomBigInt(generator, p);
                publicKeys[i] = BigInteger.ModPow(g, privateKeys[i], p);
            }
            long time = 0;
            var sw = new Stopwatch();
            sw.Start();
            time += SecretEstebleshment();
            time += KeyTransfer();
            sw.Stop();
            
            return specialExecution? time : sw.ElapsedMilliseconds;
        }

        private static long SecretEstebleshment()
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            ri[n - 1] = AlgoAndMath.RandomBigInt(generator, p);
            for (int i = 0; i < n - 1; i++) // "broadcasting"
            {
                //step #1
                // {time spender}
                
                //step #2
                ri[i] = AlgoAndMath.RandomBigInt(generator, p);
                var power = (privateKeys[i] * ri[i] * ri[n - 1]);
                //ulong t = AlgoAndMath.FastMultilication(ri[i], ri[n - 1], p);
                //t = AlgoAndMath.FastMultilication(t, privateKeys[i], p);
                si[i] = BigInteger.ModPow(publicKeys[n - 1], power, p); //AlgoAndMath.SquareAndMultiply(publicKeys[n - 1], t, p);
                var riArr = ri[i].ToByteArray();
                var siArr = si[i].ToByteArray();
                var arr = new byte[riArr.Length + siArr.Length];
                Array.Copy(riArr, 0, arr, 0, riArr.Length);
                Array.Copy(siArr, 0, arr, riArr.Length, siArr.Length);
                var hash = sha.ComputeHash(arr);

                if (_specialExecution) sw.Stop();
                //{time sender}
            }
            sw.Start();
            for (int i = 0; i < n - 1; i++)
            {
                //step #3
                var power = (privateKeys[n-1] * ri[i] * ri[n - 1]);
                //t = AlgoAndMath.FastMultilication(ri[i], ri[n - 1], p);
                //t = AlgoAndMath.FastMultilication(t, privateKeys[n - 1], p);
                var siStar = BigInteger.ModPow(publicKeys[i], power, p); //AlgoAndMath.SquareAndMultiply(publicKeys[i], t, p);
                var riArrPrime = ri[i].ToByteArray();
                var siArrPrime = siStar.ToByteArray();
                var arrPrime = new byte[riArrPrime.Length + siArrPrime.Length];
                Array.Copy(riArrPrime, 0, arrPrime, 0, riArrPrime.Length);
                Array.Copy(siArrPrime, 0, arrPrime, riArrPrime.Length, siArrPrime.Length);
                var hashPrime = sha.ComputeHash(arrPrime);
            }
            sw.Stop();
            return sw.ElapsedMilliseconds;
        }

        private static long KeyTransfer()
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            //step #1
            var secret = AlgoAndMath.RandomBigInt(generator, p); //generator.NextULongPositive(p);
            
            for (int i = 0; i < n - 1; i++)
            {
                BigInteger xi, yi;
                AlgoAndMath.SplitBigInteger(si[i], out xi, out yi);
                var ki = (AlgoAndMath.DotProduct(V(xi), ri, p) * yi) % p;
                ui[i] = (secret - ki) % p;
            }
            var hash = ComputeBigHash(secret);  
            // {time spender}

            //step #2
            for (int i = 0; i < n - 1; i++) 
            {
                BigInteger xi, yi;
                AlgoAndMath.SplitBigInteger(si[i], out xi, out yi);
                var ki = (AlgoAndMath.DotProduct(V(xi), ri, p) * yi) % p;
                var restoredSecret = (ui[i] + ki) % p;
                // big hash computation and verification
                var restoredHash = ComputeBigHash(restoredSecret);
                if (_specialExecution) sw.Stop();
            }
            return sw.ElapsedMilliseconds;
        }

        private static byte[] ComputeBigHash(BigInteger secret)
        {
            // big hash computation
            var arr = secret.ToByteArray();
            for (int i = 0; i < n; i++)
            {
                var riArr = ri[i].ToByteArray();
                Array.Resize(ref arr, arr.Length + riArr.Length);
                Array.Copy(riArr, 0, arr, arr.Length - riArr.Length, riArr.Length);
            }
            for (int i = 0; i < n - 1; i++)
            {
                var uiArr = ui[i].ToByteArray();
                Array.Resize(ref arr, arr.Length + uiArr.Length);
                Array.Copy(uiArr, 0, arr, arr.Length - uiArr.Length, uiArr.Length);
            }
            var hash = sha.ComputeHash(arr);
            return hash;
        }


        //private static ulong[] V(ulong x)
        //{
        //    var res = new ulong[n];
        //    res[0] = 1;
        //    for (int i = 1; i < n; i++)
        //    {
        //        res[i] = (res[i - 1] * x) % p;
        //    }
        //    return res;
        //}

        private static BigInteger[] V(BigInteger x)
        {
            var res = new BigInteger[n];
            res[0] = 1;
            for (int i = 1; i < n; i++)
            {
                res[i] = (res[i - 1] * x) % p;
            }
            return res;
        }
    }
}